package server;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import smash_tris.DataPacket;
import smash_tris.Player;
import smash_tris.Room;
import smash_tris.TypeOfDataPacket;

/**
 * Obiekt klasy SmashTrisManager zawiera i umożliwia operacje na danych
 * należących do serwera.
 * 
 * @author Pawel Rychly
 * 
 */
public class SmashTrisManager {

	private Vector<Room> roomsList = new Vector<Room>();
	private Vector<Player> playersList = new Vector<Player>();

	private Map<Player, ObjectOutputStream> broadcastList = Collections
			.synchronizedMap(new HashMap<Player, ObjectOutputStream>());
	private Map<Player, Connection> connectionMap = Collections
			.synchronizedMap(new HashMap<Player, Connection>());
	private Map<Room, GameRoomCommunication> gameRoomMap = Collections
			.synchronizedMap(new HashMap<Room, GameRoomCommunication>());

	/**
	 * Metoda pozwala na pobranie referencji do obiektu Room na podstawie
	 * identycznego obiektu room przesłanego ze strony klienta.
	 * 
	 * @param room
	 *            referencja do obiektu pokoju przesłanego przez klienta
	 * @return Referencja z pamięci serwera.
	 */
	public synchronized Room getRoom(Room room) {
		Room roomFromServer = null;
		synchronized (this.roomsList) {
			Iterator<Room> i = this.roomsList.iterator();
			boolean isFound = false;

			while ((!isFound) && (i.hasNext())) {
				roomFromServer = (Room) i.next();
				if (room.getRoomId().equals(roomFromServer.getRoomId())) {
					isFound = true;

				}
			}

			if (!isFound) {
				return null;
			}
		}
		return roomFromServer;
	}

	/**
	 * Metoda pozwala na utworzenie powiązania pomiędzy graczem a obiektem
	 * Connection.
	 * 
	 * @param player
	 *            Obiekt reprezentujący gracza.
	 * @param connection
	 *            Obiekt umożliwiający połączenie serwera z graczem.
	 */
	public void linkConnectionToPlayer(Player player, Connection connection) {
		synchronized (connectionMap) {
			this.connectionMap.put(player, connection);
		}
	}

	/**
	 * Metoda zwraca obiekt reprezentujący połączenie z graczem.
	 * 
	 * @param player
	 *            obiekt reprezentujący gracza.
	 * @return Obiekt typu Connection.
	 */
	public Connection getConnectionAssociatedWithPlayer(Player player) {
		return this.connectionMap.get(player);
	}

	/**
	 * Metoda umożliwia dodanie nowego pokoju gry.
	 */
	public boolean addNewRoom(Room room) {
		boolean isOccupied = false;
		Iterator<Room> iterator = roomsList.iterator();
		while ((isOccupied != true) && (iterator.hasNext())) {
			Room itemRoom = (Room) iterator.next();
			if (itemRoom.getName().equals(room.getName())) {
				isOccupied = true;
			}
		}
		if (!isOccupied) {
			roomsList.add(room);

			try {
				broadcastToAllPlayers(TypeOfDataPacket.RESET_ROOM_STATE,
						roomsList);

			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("Pokoj dodany.." + room.getName());
			return true;
		} else {
			System.out.println("exception");
			return false;
		}

	}

	/**
	 * Metoda umożliwia dodanie nowego gracza.
	 */
	public void addNewPlayer(Player player) {
		playersList.add(player);
		try {
			broadcastToAllPlayers(TypeOfDataPacket.ADD_PLAYER, playersList);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Metoda zwraca listę pokoi.
	 * 
	 * @return
	 */
	public List<Room> getRoomList() {
		return roomsList;
	}

	/**
	 * Metoda zwraca listę graczy.
	 * 
	 * @return Lista graczy.
	 */
	public List<Player> getPlayersList() {
		return playersList;
	}

	/**
	 * Metoda umożliwia usunięcie gracza. TODO implementacja metody. Room room
	 * Pokój,który ma zostać usunięty. TODO Dopracować szczegóły metody.
	 */
	public synchronized boolean removeRoom(Room room) {
		return this.roomsList.remove(room);
	}

	/**
	 * Metoda umożliwia dodanie nowego socketu do listy socketów dla broadcastu.
	 * 
	 * @param socket
	 */
	public void addOutputStreamToBroadcast(Player player, ObjectOutputStream out) {

		this.broadcastList.put(player, out);
	}

	/**
	 * Metoda umożliwia usunięcie pokoju. TODO implementacja metody.
	 */
	public void removePlayer(Player player) {

		try {
			// for(Room room: this.roomsList){
			Iterator<Room> i = this.roomsList.iterator();
			while (i.hasNext()) {
				Room room = (Room) i.next();
				room.removePlayer(player);
				if (room.getRoomMaster().equals(player)) {
					roomsList.remove(room);
				}
			}

			this.playersList.remove(player);
			this.connectionMap.remove(player);
			synchronized (broadcastList) {
				this.broadcastList.remove(player);
			}

			// }
		} catch (ConcurrentModificationException e) {
			removePlayer(player);
		}

		try {
			this.broadcastToAllPlayers(TypeOfDataPacket.RESET_ROOM_STATE,
					this.roomsList);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Tworzy obiekt umożliwiający toczenie rozgrywki.
	 * 
	 * @param room
	 *            pokój
	 * @param player
	 *            gracz tworzący pokój
	 */
	public void createGameRoomCommunication(Room room, ObjectInputStream in,
			ObjectOutputStream out) {
		GameRoomCommunication gameCommunication = new GameRoomCommunication(
				room, in, out, this);
		gameRoomMap.put(room, gameCommunication);
	}

	/**
	 * Metoda umożliwia dołączenie do rozgrywki drugiemu graczowi.
	 * 
	 * @param room
	 * @param in
	 * @param out
	 * @param player
	 */
	public void joinTogameRoomCommunication(Room room, ObjectInputStream in,
			ObjectOutputStream out, Player player) {

		GameRoomCommunication gameCommunication = gameRoomMap.get(room);

		if (gameCommunication != null) {
			gameCommunication.joinToGameRoom(in, out);
		}
		try {
			broadcastToAllPlayers(TypeOfDataPacket.RESET_ROOM_STATE,
					this.roomsList);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public void removeFromGameRoomCommunication(Room room, Player player) {
		GameRoomCommunication gameCommunication = gameRoomMap.get(room);
		if (gameCommunication != null) {
			Room r1 = gameCommunication.removeFromRoom(player, room);
			try {
				broadcastToAllPlayers(TypeOfDataPacket.RESET_ROOM_STATE,
						this.roomsList);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}
	}

	/**
	 * Metoda pozwala na określenie gotowości gracza do gry.
	 * 
	 * @param room
	 *            pokój w któym rozgrywa się gra.
	 * @param player
	 *            gracz.
	 * @return 2 - jeśli gra się rozpoczęła. 1 - stan gotowości do gry. 0 -
	 *         gracz nie jest gotowy do gry.
	 */
	public Integer setReady(Room room, Player player) {
		GameRoomCommunication gameCommunication = gameRoomMap.get(room);
		return gameCommunication.setReady(player);
	}

	/**
	 * send to players
	 * 
	 * @param players
	 * @param type
	 * @param object
	 */
	public void broadcastToPlayers(ArrayList<Player> players,
			TypeOfDataPacket type, Object object) {

		DataPacket data = new DataPacket(type, object);
		for (Player player1 : players) {
			for (Player player : this.playersList) {
				if (player1.getId() == player.getId()) {
					synchronized (broadcastList) {
						ObjectOutputStream out = this.broadcastList.get(player);
						try {
							out.writeObject(data);
							out.flush();
						} catch (IOException e) {
							e.printStackTrace();
						}

					}
				}
			}
		}
	}

	/**
	 * Metoda czyści pokój gry.
	 * 
	 * @param room
	 */
	public void removeGameRoom(Room room) {
		gameRoomMap.remove(room);
		for (Player player : room.getPlayers()) {
			room.removePlayer(player);
		}

		Room r1 = this.getRoom(room);
		synchronized (roomsList) {
			roomsList.remove(r1);
			// roomsList.add(room);

		}
		try {
			broadcastToAllPlayers(TypeOfDataPacket.RESET_ROOM_STATE, roomsList);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	/**
	 * Wysyła wiadomość do wszystkich graczy
	 * 
	 * @param type
	 *            type of message
	 * @param object
	 *            additional data
	 * @throws IOException
	 */
	private void broadcastToAllPlayers(TypeOfDataPacket type, Object object)
			throws IOException {

		DataPacket data = new DataPacket(type, object);
		/*
		 * @SuppressWarnings("unchecked") Vector<Room> rooms =(Vector<Room>)
		 * data.getData();
		 * 
		 * System.out.println(rooms.size());
		 * System.out.println("---------------------------"); for (Room r:rooms)
		 * { System.out.println("name:\t" +r.getName()); }
		 */
		for (Player player : this.playersList) {

			synchronized (broadcastList) {
				ObjectOutputStream out = this.broadcastList.get(player);
				try {
					out.writeObject(data);
					out.flush();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

		}

		for (Player player : this.playersList) {
			ObjectOutputStream out = this.broadcastList.get(player);
			out.reset();
		}
	}

	public boolean getIsRunning(Room room) {
		GameRoomCommunication gameCommunication = gameRoomMap.get(room);
		return gameCommunication.isRunning();

	}

	public void notifyPlayerInRoomCommunication(Room room) {
		GameRoomCommunication gameCommunication = gameRoomMap.get(room);

		gameCommunication.notifyPlayer();
	}

	public void joinGameThread(Room room) {
		GameRoomCommunication gameCommunication = gameRoomMap.get(room);
		gameCommunication.joinGameThread();

	}

	public void setStart(Room room) {
		GameRoomCommunication gameCommunication = gameRoomMap.get(room);
		gameCommunication.setStart();

	}

}
